import librosa
import numpy as np
import matplotlib.pyplot as plt


class ChordDetector:
    
    def detect(self, audio_path):
        self.audio_path = audio_path
        self.loadMp3()
        self.compute_chromagram()
        self.detect_chord_candidates()
        self.detect_chord()
    """
    步骤 2：加载并预处理音频
    """

    def loadMp3(self):
        # 加载MP3文件（自动重采样到默认22050Hz）
        # audio_path = "guitar.mp3"
        self.y, self.sr = librosa.load(self.audio_path, duration=30)  # 仅分析前30秒

        # 预处理：降噪和重采样（可选）
        self.y_clean = librosa.effects.preemphasis(self.y)  # 预加重增强高频

    """
    步骤 3：计算色度特征（Chromagram）
    色度特征（Chromagram）将频谱映射到12个音级（对应C, C#, D, ..., B），是和弦分析的核心特征。
    """

    def compute_chromagram(self):
        # 使用CQT（Constant-Q Transform）提升低频分辨率
        self.chroma_cqt = librosa.feature.chroma_cqt(y=self.y_clean, sr=self.sr, bins_per_octave=36)
        # 可视化色度图
        plt.figure(figsize=(12, 4))
        librosa.display.specshow(self.chroma_cqt, sr=self.sr, x_axis="time", y_axis="chroma")
        plt.colorbar()
        plt.title("Chromagram (CQT-based)")
        plt.show()
        
    
    """
    ### **步骤 4：和弦候选检测**
    通过色度特征的峰值检测确定和弦候选：
    """
    def detect_chord_candidates(self):
        # 计算时间平均色度能量
        chroma_mean = np.mean(self.chroma_cqt, axis=1)
        # 找到能量最高的3个音级（假设为和弦根音）
        top_notes = np.argsort(chroma_mean)[-3:]
        note_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
        print("Top notes:", [note_names[i] for i in top_notes])

    """
    ### **步骤 5：和弦类型识别（简单规则）**
    根据色度分布模式推断和弦类型（如大三和弦、小三和弦）：
    """
    def detect_chord(self, chroma_vector, threshold=0.8):
        # 阈值过滤低能量音级
        mask = chroma_vector > threshold * np.max(chroma_vector)
        active_notes = np.where(mask)[0]

        # 和弦规则库（示例）
        chord_patterns = {
            "major": [0, 4, 7],  # 大三度+小三度
            "minor": [0, 3, 7],  # 小三度+大三度
        }

        # 匹配最接近的和弦类型
        for chord, pattern in chord_patterns.items():
            if set(active_notes) == set((active_notes[0] + np.array(pattern)) % 12):
                root = note_names[active_notes[0]]
                return f"{root} {chord}"
        return "Unknown"

    # def detect_chord_type(self, active_notes):
    #     # 分析每个时间帧的和弦（示例取中间帧）
    #     current_frame = self.chroma_cqt[:, self.chroma_cqt.shape[1] // 2]
    #     current_chord = self.detect_chord(current_frame)
    #     print(f"Detected chord: {current_chord}")

    #     ### **步骤 6：结合节拍分割优化**
    #     # 通过节拍分割减少计算量，按节拍区间分析和弦：

    #     # 检测节拍位置
    #     tempo, beat_frames = librosa.beat.beat_track(y=self.y_clean, sr=self.sr)
    #     beat_times = librosa.frames_to_time(beat_frames, sr=sr)

    #     # 按节拍区间分析和弦
    #     for i in range(len(beat_frames) - 1):
    #         start = beat_frames[i]
    #         end = beat_frames[i + 1]
    #         chroma_segment = np.mean(self.chroma_cqt[:, start:end], axis=1)
    #         chord = self.detect_chord(chroma_segment)
    #         print(f"Beat {i+1}: {chord} ({beat_times[i]:.2f}s)")

if __name__ == "__main__":
    cd = ChordDetector()
    audio_path = "./mp3/miya_01.mp3"
    cd.detect(audio_path)


